package code;

import java.nio.ByteOrder;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.util.internal.ObjectUtil;
import utils.ByteUtils;

public class MyLengthFieldPrepender extends MessageToByteEncoder<ByteBuf> {

    private final ByteOrder byteOrder;
    private final int lengthFieldLength = 4;
    private final boolean lengthIncludesLengthFieldLength;
    private final int lengthAdjustment;

    /**
     * Creates a new instance.
     *
     *
     */
    public MyLengthFieldPrepender() {
        this(false);
    }

    /**
     * Creates a new instance.
     *
     * @param lengthIncludesLengthFieldLength
     *                          if {@code true}, the length of the prepended
     *                          length field is added to the value of the
     *                          prepended length field.
     *
     */
    public MyLengthFieldPrepender(boolean lengthIncludesLengthFieldLength) {
        this(0, lengthIncludesLengthFieldLength);
    }

    /**
     * Creates a new instance.
     *
     * @param lengthAdjustment  the compensation value to add to the value
     *                          of the length field
     *
     * @throws IllegalArgumentException
     *         if {@code lengthFieldLength} is not 1, 2, 3, 4, or 8
     */
    public MyLengthFieldPrepender(int lengthAdjustment) {
        this(lengthAdjustment, false);
    }

    /**
     * Creates a new instance.
     *
     * @param lengthAdjustment  the compensation value to add to the value
     *                          of the length field
     * @param lengthIncludesLengthFieldLength
     *                          if {@code true}, the length of the prepended
     *                          length field is added to the value of the
     *                          prepended length field.
     *
     * @throws IllegalArgumentException
     *         if {@code lengthFieldLength} is not 1, 2, 3, 4, or 8
     */
    public MyLengthFieldPrepender(int lengthAdjustment, boolean lengthIncludesLengthFieldLength) {
        this(ByteOrder.BIG_ENDIAN, lengthAdjustment, lengthIncludesLengthFieldLength);
    }

    /**
     * Creates a new instance.
     *
     * @param byteOrder         the {@link ByteOrder} of the length field
     * @param lengthAdjustment  the compensation value to add to the value
     *                          of the length field
     * @param lengthIncludesLengthFieldLength
     *                          if {@code true}, the length of the prepended
     *                          length field is added to the value of the
     *                          prepended length field.
     *
     */
    public MyLengthFieldPrepender(
            ByteOrder byteOrder, 
            int lengthAdjustment, boolean lengthIncludesLengthFieldLength) {
        ObjectUtil.checkNotNull(byteOrder, "byteOrder");

        this.byteOrder = byteOrder;
        this.lengthIncludesLengthFieldLength = lengthIncludesLengthFieldLength;
        this.lengthAdjustment = lengthAdjustment;
    }

    @Override
    protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception {
        int length = msg.readableBytes() + lengthAdjustment;
        if (lengthIncludesLengthFieldLength) {
            length += lengthFieldLength;
        }

        if (length < 0) {
            throw new IllegalArgumentException(
                    "Adjusted frame length (" + length + ") is less than zero");
        }

        switch (lengthFieldLength) {
        case 1:
            if (length >= 256) {
                throw new IllegalArgumentException(
                        "length does not fit into a byte: " + length);
            }
            out.writeByte((byte) length);
            break;
        case 4:
        	ByteUtils.writeVariaInt(length, out);
//            out.writeInt(length);
            break;
        case 8:
        	ByteUtils.writeVariaLong(length, out);
//            out.writeLong(length);
            break;
        default:
            throw new Error("should not reach here");
        }

        out.writeBytes(msg, msg.readerIndex(), msg.readableBytes());
    }

    @Override
    protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) throws Exception {
        return super.allocateBuffer(ctx, msg, preferDirect).order(byteOrder);
    }
}
